package com.kool.kadmin.system.service;

import com.kool.kadmin.common.bean.ObjectResult;
import com.kool.kadmin.common.bean.PageListResult;
import com.kool.kadmin.common.bean.Result;
import com.kool.kadmin.common.cache.CacheRepo;
import com.kool.kadmin.common.cache.CacheRepoFactory;
import com.kool.kadmin.common.cache.TokenVo;
import com.kool.kadmin.common.constant.ErrorCode;
import com.kool.kadmin.common.exception.AppException;
import com.kool.kadmin.common.util.*;
import com.kool.kadmin.system.api.vo.UserVo;
import com.kool.kadmin.system.bean.SysUserDo;
import com.kool.kadmin.system.cache.UserCache;
import com.kool.kadmin.system.constant.UserStateEnum;
import com.kool.kadmin.system.dao.SysUserDao;
import com.kool.kadmin.system.request.*;
import com.kool.kadmin.system.util.UserTransfer;
import com.kool.kadmin.system.vo.FileVo;
import com.kool.kadmin.system.vo.PreloginVo;
import com.kool.kadmin.system.vo.UserConditionVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.*;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;

/**
 * @author luyu
 */
@Service
@Slf4j
public class UserService {
    @Resource
    private SysUserDao userDao;
    @Resource
    private UserCache userCache;
    @Value("${common.cache_repo}")
    private String cacheRepoType;

    /**
     * 最大密码错误次数
     */
    private static final int MAX_ERROR_TIMES = 5;

    @Value("${common.static.file}")
    private String staticFilePath;
    @Value("${sys.url.avatar_url}")
    private String avatarUrl;

    /**
     * 增加用户
     *
     * @param addUserReq
     * @return
     */
    public Result addUser(AddUserReq addUserReq) {
        String loginName = addUserReq.getLoginName();
        String userName = addUserReq.getUserName();
        String phone = addUserReq.getPhone();
        String avatar = addUserReq.getAvatar();
        String svnUser = addUserReq.getSvnUser();
        String svnPwd = addUserReq.getSvnPwd() != null ? SecurityUtil.base64Decode(addUserReq.getSvnPwd()) : null;

        //生成AES私钥
        String pk = SecurityUtil.generateAESKey();
        svnPwd = svnPwd != null ? SecurityUtil.encryptAES(svnPwd, pk) : null;

        //用户名是否存在
        SysUserDo userDo = userDao.getByLoginName(loginName);
        if (userDo != null) {
//            ResultUtil.error(ErrorCode.BUSINESS_ERROR, "用户名已存在");
            throw new AppException(ErrorCode.BUSINESS_ERROR.getCode(), "登录名已存在");
        }

        //初始密码为用户名
        String pwd = loginName;
        // 生成随机数N
        int N = 3;
        // 生成加密盐
        String salt = getSalt();
        // 密码密文=sha(密码+|+加密盐,N)
        String encryptPwd = SecurityUtil.sha(pwd + "|" + salt, N);

        userDo = new SysUserDo();
        userDo.setLoginName(loginName)
                .setUserName(userName)
                .setPhone(phone)
                .setPwd(encryptPwd)
                .setSalt(salt)
                .setNewSalt(salt)
                .setHashTimes(N)
                .setErrorTimes(0)
                .setAvatar(avatar)
                .setState(UserStateEnum.ACTIVE.getCode())
                .setSvnUser(svnUser)
                .setSvnPwd(svnPwd)
                .setPrivateKey(pk);

        userDao.insert(userDo);

        return ResultUtil.success();
    }

    /**
     * 生成加密盐
     *
     * @return
     */
    private String getSalt() {
        String salt = StringUtil.getUUID().substring(0, 5);
        return salt;
    }

    /**
     * 生成随机数N
     *
     * @return
     */
    private int getN() {
        Random random = new Random(System.currentTimeMillis());
        int N = Math.abs(random.nextInt()) % 10000 + 2;
        return N;
    }

    /**
     * 预登录
     *
     * @param request
     * @return
     */
    public ObjectResult<PreloginVo> prelogin(PreloginReq request) {
        String loginName = request.getLoginName();
        //查询用户信息
        SysUserDo userDo = userDao.getByLoginName(loginName);
        if (null == userDo) {
            throw new AppException(ErrorCode.BUSINESS_ERROR, "用户不存在");
        }

        if (!userDo.getState().equals(UserStateEnum.ACTIVE.getCode())) {
            throw new AppException(ErrorCode.BUSINESS_ERROR, "用户状态异常，请联系管理员");
        }

        return ResultUtil.success(new PreloginVo()
                .setLoginName(loginName)
                .setHashTimes(userDo.getHashTimes() - 1)
                .setOldSalt(userDo.getSalt())
                .setNewSalt(userDo.getNewSalt()));
    }

    /**
     * 登录
     *
     * @param request
     * @return
     */
    public ObjectResult<TokenVo> login(LoginReq request) {
        String loginName = request.getLoginName();
        String pwdA = SecurityUtil.base64Decode(request.getPwdA());
        String pwdB = SecurityUtil.base64Decode(request.getPwdB());

        //查询用户信息
        SysUserDo userDo = userDao.getByLoginName(loginName);
        if (null == userDo) {
            throw new AppException(ErrorCode.BUSINESS_ERROR, "用户不存在");
        }

        int N = userDo.getHashTimes();

        // sha加密1次
        String sPwdA = SecurityUtil.sha(pwdA, 1);

        // 验证密码是否正确
        if (!sPwdA.equals(userDo.getPwd())) {
            // 密码错误，更新密码错误次数
            int errorTimes = userDo.getErrorTimes() + 1;
            String errorMsg = "密码错误";
            if (errorTimes == MAX_ERROR_TIMES) {
                //达到最大错误次数，锁定用户
                userDo.setState(UserStateEnum.LOCK.getCode());
                errorMsg = String.format(":密码错误次数达到%s次，账户被锁定", MAX_ERROR_TIMES);
            }
            userDao.addErrorTimes(loginName, userDo.getState(), DateUtil.dateString(new Date()) + errorMsg);
            throw new AppException(ErrorCode.BUSINESS_ERROR, errorMsg);
        }

        if (N == 2) {
            N = getN();
            pwdB = SecurityUtil.sha(pwdB, N - 1);
        } else {
            N = N - 1;
        }
        // 更新N/新盐/旧盐/密码/密码错误次数
        userDo.setHashTimes(N).setSalt(userDo.getNewSalt()).setNewSalt(getSalt()).setPwd(pwdB).setErrorTimes(0).setCmt(DateUtil.dateString(new Date()) + ":登录");
        userDao.updateLogin(userDo);

        //token
        String token = getToken();

        TokenVo tokenVo = new TokenVo()
                .setId(userDo.getId())
                .setLoginName(loginName)
                .setUserName(userDo.getUserName())
                .setToken(token)
                .setCreatedTime(new Date())
                .setAvatar(userDo.getAvatar());
        CacheRepo cacheRepo = CacheRepoFactory.getCacheRepo(cacheRepoType);
        cacheRepo.putToken(userDo.getId(), tokenVo);

        return ResultUtil.success(tokenVo);
    }

    private String getToken() {
        String token = SecurityUtil.base64Encode(StringUtil.getUUID());
        return token;
    }

    /**
     * 关闭用户
     *
     * @param request
     * @return
     */
    public Result closeUser(CloseUserReq request) {
        long id = request.getId();

        SysUserDo userDo = userDao.getById(id);
        if (null == userDo) {
            throw new AppException(ErrorCode.BUSINESS_ERROR, "用户不存在");
        }

        userDo.setState(UserStateEnum.CLOSE.getCode())
                .setCmt(DateUtil.dateString(new Date()) + ":关闭用户");

        userDao.updateState(userDo);

        return ResultUtil.success();
    }

    /**
     * 分页查询用户列表
     *
     * @param request
     * @return
     */
    public PageListResult<UserVo> getUserPage(GetUserPageReq request) {
        Date createdTimeBegin = request.getCreatedTimeBegin();
        Date createdTimeEnd = request.getCreatedTimeEnd();
        createdTimeBegin = DateUtil.getDateStart(createdTimeBegin);
        createdTimeEnd = DateUtil.getDateEnd(createdTimeEnd);
        String loginName = request.getLoginName();
        Integer state = request.getState();
        UserConditionVo condition = new UserConditionVo();
        condition.setLoginName(loginName)
                .setState(state)
                .setUserName(request.getUserName())
                .setPhone(request.getPhone())
                .setCreatedTimeBegin(createdTimeBegin)
                .setCreatedTimeEnd(createdTimeEnd);

        int page = request.getCurrentPage();
        int pageSize = request.getPageSize();

        int offset = (page - 1) * pageSize;
        int total = userDao.getTotal(condition);

        List<SysUserDo> userDos = userDao.getUsersPage(condition, offset, pageSize);

        List<UserVo> userVos = userDos.stream().map(UserVo::toUserVo).collect(Collectors.toList());

        return ResultUtil.success(userVos, page, pageSize, total);
    }

    /**
     * 查询用户详情
     *
     * @param request
     * @return
     */
    public ObjectResult<UserVo> getUser(GetUserReq request) {
        long id = request.getId();
        UserVo userVo = getUserById(id);
        return ResultUtil.success(userVo);
    }

    /**
     * 重置密码为默认值
     *
     * @param request
     * @return
     */
    public Result resetPwd(ResetPwdReq request) {
        long id = request.getId();

        SysUserDo userDo = userDao.getById(id);
        if (null == userDo) {
            throw new AppException(ErrorCode.BUSINESS_ERROR, "用户不存在");
        }

        //初始密码为用户名
        String pwd = userDo.getLoginName();
        // 生成随机数N
        int N = 3;
        // 生成加密盐
        String salt = getSalt();
        // 密码密文=sha(密码+|+加密盐,N)
        String encryptPwd = SecurityUtil.sha(pwd + "|" + salt, N);

        userDo.setPwd(encryptPwd)
                .setSalt(salt)
                .setNewSalt(salt)
                .setHashTimes(N)
                .setErrorTimes(0)
                .setState(UserStateEnum.ACTIVE.getCode())
                .setCmt(DateUtil.dateString(new Date()) + ":重置密码");
        userDao.updatePwd(userDo);

        return ResultUtil.success();
    }

    /**
     * 修改密码
     *
     * @param request
     * @return
     */
    public Result modifyPwd(ModifyPwdReq request) {
        long id = request.getId();
        String oldPwd = SecurityUtil.base64Decode(request.getOldPwd());
        String newPwd = SecurityUtil.base64Decode(request.getNewPwd());
        String confirmPwd = SecurityUtil.base64Decode(request.getConfirmPwd());

        if (!newPwd.equals(confirmPwd)) {
            throw new AppException(ErrorCode.BUSINESS_ERROR, "密码不一致");
        }

        SysUserDo userDo = userDao.getById(id);
        if (null == userDo) {
            throw new AppException(ErrorCode.BUSINESS_ERROR, "用户不存在");
        }

        //验证旧密码
        oldPwd = SecurityUtil.sha(oldPwd + "|" + userDo.getSalt(), userDo.getHashTimes());
        if (!oldPwd.equals(userDo.getPwd())) {
            throw new AppException(ErrorCode.BUSINESS_ERROR, "旧密码错误");
        }


        //修改密码
        String pwd = newPwd;
        // 生成随机数N
        int N = 3;
        // 生成加密盐
        String salt = getSalt();
        // 密码密文=sha(密码+|+加密盐,N)
        String encryptPwd = SecurityUtil.sha(pwd + "|" + salt, N);

        userDo.setPwd(encryptPwd)
                .setSalt(salt)
                .setNewSalt(salt)
                .setHashTimes(N)
                .setErrorTimes(0)
                .setState(UserStateEnum.ACTIVE.getCode())
                .setCmt(DateUtil.dateString(new Date()) + ":修改密码");
        userDao.updatePwd(userDo);
        //更新缓存
        userCache.setUserByName(userDo.getUserName(), UserVo.toUserVo(userDo));

        return ResultUtil.success();
    }

    /**
     * 管理员修改用户信息
     *
     * @param request
     * @return
     */
    public Result modifyUser(ModifyUserReq request) {
        long id = request.getId();
        String phone = request.getPhone();
        String email = request.getEmail();
        String avatar = request.getAvatar();
        String userName = request.getUserName();

        SysUserDo userDo = userDao.getById(id);
        if (null == userDo) {
            throw new AppException(ErrorCode.BUSINESS_ERROR, "旧密码错误");
        }
        userDo.setPhone(phone)
                .setEmail(email)
                .setAvatar(avatar)
                .setCmt(DateUtil.dateString(new Date()) + ":修改用户信息")
                .setUserName(userName);

        userDao.updateInfo(userDo);

        //更新缓存
        userCache.setUserByName(userName, UserVo.toUserVo(userDo));
        return ResultUtil.success();
    }

    /**
     * 退出登录
     *
     * @param request
     * @return
     */
    public Result logout(LogoutReq request) {
        CacheRepo cacheRepo = CacheRepoFactory.getCacheRepo(cacheRepoType);
        cacheRepo.removeToken(request.getId(), request.getToken());
        return ResultUtil.success();
    }

    /**
     * 上传头像
     *
     * @param uploadFile
     * @return
     */
    public FileVo uploadFace(MultipartFile uploadFile) {
        FileVo fileVo = new FileVo();
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        // 新文件名
        String newFileName = StringUtil.getUUID() + ".jpg";
        try {
            // 文件类型
            String contentType = uploadFile.getContentType();
            fileVo.setFileType(contentType);
            // 头像文件存放路径
            String avatarFilePath = staticFilePath + FileUtil.getSeparator() + "face" + FileUtil.getSeparator();
            File fDir = new File(avatarFilePath);
            if (fDir.exists() == false) {
                fDir.mkdirs();
            }
            // 文件保存路径
            String filePath = new StringBuffer().append(avatarFilePath).append(newFileName)
                    .toString();
            InputStream is = uploadFile.getInputStream();
            bis = new BufferedInputStream(is);
            bos = new BufferedOutputStream(new FileOutputStream(new File(filePath)));
            byte[] buff = new byte[1024 * 16];
            int hasRead = 0;
            while ((hasRead = bis.read(buff)) != -1) {
                bos.write(buff, 0, hasRead);
            }

            bos.flush();
            bos.close();
            bis.close();
        } catch (IOException e) {
            log.error("上传头像失败！", e);
            try {
                if (bos != null) {
                    bos.close();
                }
            } catch (IOException e1) {
                log.error("上传头像失败！", e1);
            }
            try {
                if (bis != null) {
                    bis.close();
                }
            } catch (IOException e1) {
                log.error("上传头像失败！", e1);
            }
            log.error(" 上传头像失败！");
            throw new AppException(ErrorCode.SYSTEM_ERROR, StringUtil.getUUID());
        }

        fileVo.setFileName(newFileName);
        fileVo.setFileUrl(avatarUrl + newFileName);
        return fileVo;
    }

    /**
     * 查询所有用户
     *
     * @return
     */
    public List<UserVo> getAll() {
        List<SysUserDo> userDos = userDao.getAll();
        if (userDos == null) {
            return null;
        }
        List<UserVo> userVos = userDos.stream().map(UserVo::toUserVo).collect(Collectors.toList());
        return userVos;
    }

    /**
     * 校验token
     *
     * @param userId
     * @param token
     * @return
     */
    public boolean validateToken(Long userId, String token) {
        CacheRepo cacheRepo = CacheRepoFactory.getCacheRepo(cacheRepoType);
        return cacheRepo.isContain(userId, token);
    }

    /**
     * 根据用户id查询用户信息
     *
     * @param id
     * @return
     */
    public UserVo getUserById(Long id) {
        SysUserDo userDo = userDao.getById(id);
        if (null == userDo) {
            return null;
        }

        UserVo userVo = UserTransfer.userDoToUserVo(userDo);
        if (userDo.getSvnPwd() != null && userDo.getPrivateKey() != null) {
            userVo.setSvnPwd(SecurityUtil.decryptAES(userDo.getSvnPwd(), userDo.getPrivateKey()));
        }
        return userVo;
    }

    /**
     * 更新个人信息
     *
     * @param request
     * @return
     */
    public Result updateMyInfo(UpdateMyInfoReq request) {
        long id = request.getId();
        String phone = request.getPhone();
        String email = request.getEmail();
        String avatar = request.getAvatar();
        String userName = request.getUserName();
        String svnUser = request.getSvnUser();
        String svnPwd = request.getSvnPwd() != null ? SecurityUtil.base64Decode(request.getSvnPwd()) : null;

        SysUserDo userDo = userDao.getById(id);
        if (null == userDo) {
            throw new AppException(ErrorCode.BUSINESS_ERROR, "旧密码错误");
        }
        //获取AES私钥
        String pk = userDo.getPrivateKey();
        if (StringUtil.isEmpty(pk)) {
            pk = SecurityUtil.generateAESKey();
        }
        svnPwd = svnPwd != null ? SecurityUtil.encryptAES(svnPwd, pk) : "";

        userDo.setPhone(phone)
                .setEmail(email)
                .setAvatar(avatar)
                .setCmt(DateUtil.dateString(new Date()) + ":更新个人信息")
                .setSvnUser(svnUser)
                .setSvnPwd(svnPwd)
                .setPrivateKey(pk)
                .setUserName(userName);

        userDao.updateInfo(userDo);
        //更新缓存
        userCache.setUserByName(userName, UserVo.toUserVo(userDo));

        return ResultUtil.success();
    }

    /**
     * 根据用户姓名查询用户信息
     *
     * @param userName
     * @return
     */
    public UserVo getUserByName(String userName) {
        SysUserDo userDo = userDao.getByUserName(userName);

        if (userDo == null) {
            return null;
        } else {
            return UserVo.toUserVo(userDo);
        }
    }
}
